Node.jsのORMライブラリのPrismaを使って、ECS(Fargate)からAuroraにクエリを実行してみた

Node.jsのORMライブラリのPrismaを使って、ECS(Fargate)からAuroraにクエリを実行してみた

Clock Icon2024.10.07

こんにちは、ゲームソリューション部のsoraです。
今回は、Node.jsのORMライブラリのPrismaを使って、ECS(Fargate)からAuroraにクエリを実行してみたことについて書いていきます。

Auroraでのテーブル作成

APIサーバからAurora Serverlessにデータを挿入するためのテーブルを作成します。
今回はMySQLクライアントをインストールしたEC2から操作しました。

CREATE DATABASE mydatabase;
CREATE TABLE prefectures (
    id INT AUTO_INCREMENT PRIMARY KEY,
    prefecture VARCHAR(255),
    prefectural_capital VARCHAR(255)
);

APIサーバの構築

ECS(Fargate)でAPIサーバを構築します。
以下ソースコードです。
説明はコメント内に記載しています。

今回はTypeScriptを使用しています。
ロガーとしてpino、フレームワークとしてExpressを使用しています。

index.ts
import express from 'express';
import fs from 'fs';
import 'dotenv/config';
import { PrismaClient } from '@prisma/client';

// ログ出力ファイル指定
const logFileStream = fs.createWriteStream('/app/app.log', { flags: 'a' });
const logger = require('pino')(logFileStream);

const app = express();

// リクエストを受けるポート
const PORT = 3000;

// Feederテスト用
app.get('/feeder-test', async (req, res) => {
    interface FeederTestQueryParams {
        prefecture?: string;
        prefectural_capital?: string;
    }

    const {prefecture, prefectural_capital} = req.query as FeederTestQueryParams;
    const prisma = new PrismaClient();

    try {
        // 左側のprefectureとprefectural_capitalはDBのカラム名と一致させる
        // createはデータのINSERT
        await prisma.Prefectures.create({
            data: {
                prefecture: prefecture,
                prefectural_capital: prefectural_capital,
            },
        });
        logger.info({
            msg: 'Insert OK',
            prefecture: prefecture,
            prefectural_capital: prefectural_capital
        });

        // findManyはSELECT
        const select_result = await prisma.Prefectures.findMany();
        logger.info({
            msg: 'Select OK',
            result: select_result
        });

        res.status(200).json({ message: 'OK'});
        return;

    } catch (error) {
        const err = error as Error;
        logger.error({
            msg: 'Error executing query',
            errMessage: err.message,
            errStack: err.stack
        });
        res.status(500).json({ error: 'Database query failed' });
        return;

    } finally {
        await prisma.$disconnect();
    }
});

app.use((req, res) => {
    logger.error('Not Found:', req.originalUrl);
    res.status(404).json({ message: 'Not Found' });
});

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

package.json
package.json
{
    "name": "ts",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "start": "tsx index.ts"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "typescript": "^5.6.2"
    },
    "dependencies": {
      "@prisma/client": "^5.20.0",
      "@types/express": "^5.0.0",
      "@types/node": "^22.7.4",
      "dotenv": "^16.4.5",
      "express": "^4.21.0",
      "fs": "^0.0.1-security",
      "pino": "^9.4.0",
      "prisma": "^5.20.0",
      "tsx": "^4.19.1"
    }
  }

今回ORMライブラリとしてPrismaを使用します。
Prisma CLIとPrisma Clientをインストールして、Prismaの初期化をします。

npm install prisma
npm install @prisma/client

npx prisma init

初期化すると、prismaディレクトリが作成されて、その中のschema.prismaファイルにデータベースのスキーマを定義します。

schema.prisma

generator client {
    provider = "prisma-client-js"
  }

  datasource db {
    provider = "mysql"
    url      = env("DATABASE_URL")
  }

  model Prefectures {
    id  Int @id @default(autoincrement())
    prefecture  String
    prefectural_capital String
    @@map("prefectures")
  }

schema.prismaファイルにて参照しているDATABASE_URL.envに記載します。

.env

DATABASE_URL="mysql://[ユーザ名]:[パスワード]@[エンドポイント]:[ポート番号]/[データベース名]"

最後にECSで起動するため、Dockerfileを作成します。
Prismaを使用するため、Prisma Clientを生成しておきます。

dockerfile
FROM node:alpine

WORKDIR /app

RUN apk update

COPY /ts .
RUN npm install
# Prisma Clientの生成
RUN npx prisma generate

CMD ["npm", "run", "start"]

上記コードを使用してECRへイメージをpush、ECSでタスク定義、サービスとして起動します。
ちなみに、ログを確認するためにECS Execしようとしたところ、alpineイメージだとbashが入っていなかったため、shで接続しました。

$ aws ecs update-service --region [リージョン] --cluster [クラスタ名] --service [サービス名] --enable-execute-command
$ aws ecs describe-services --cluster [クラスタ名] --service [サービス名] | grep enableExecuteCommand
"enableExecuteCommand": true

# /bin/bashではなく/bin/sh
$ aws ecs execute-command \
  --cluster [クラスタ名] \
  --task [タスクARN] \
  --container [コンテナ名] \
  --command "/bin/sh" \
  --interactive

実行

サービスが起動している状態で、必要なパラメータを含めてリクエストを実行します。
http://{タスクのパブリックIPアドレス}:3000/feeder-test?prefecture=Hyogo&prefectural_capital=Kobe
http://{タスクのパブリックIPアドレス}:3000/feeder-test?prefecture=Kanagawa&prefectural_capital=Yokohama

{ message: 'OK'}が返ってきたため、テーブルを確認してみるとデータが登録されていました。

mysql> SELECT * from prefectures;
+----+------------+---------------------+
| id | prefecture | prefectural_capital |
+----+------------+---------------------+
|  1 | Hyogo      | Kobe                |
|  2 | Kanagawa   | Yokohama            |
+----+------------+---------------------+
2 rows in set (0.01 sec)

APIサーバにて出力したアプリログを確認してみると、データの登録、データの取得ができていることが確認できました。

app.log
{"level":30,"time":1728275785737,"pid":41,"hostname":"ip-10-0-11-67.ap-northeast-1.compute.internal","msg":"Insert OK","prefecture":"Hyogo","prefectural_capital":"Kobe"}
{"level":30,"time":1728275785739,"pid":41,"hostname":"ip-10-0-11-67.ap-northeast-1.compute.internal","msg":"Select OK","result":[{"id":1,"prefecture":"Hyogo","prefectural_capital":"Kobe"}]}
{"level":30,"time":1728275850314,"pid":41,"hostname":"ip-10-0-11-67.ap-northeast-1.compute.internal","msg":"Insert OK","prefecture":"Kanagawa","prefectural_capital":"Yokohama"}
{"level":30,"time":1728275850317,"pid":41,"hostname":"ip-10-0-11-67.ap-northeast-1.compute.internal","msg":"Select OK","result":[{"id":1,"prefecture":"Hyogo","prefectural_capital":"Kobe"},{"id":2,"prefecture":"Kanagawa","prefectural_capital":"Yokohama"}]}

最後に

今回は、Node.jsのORMライブラリのPrismaを使って、ECS(Fargate)からAuroraにクエリを実行してみたことを記事にしました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.